/**
 * Created by obesitychow on 8/15/23
 */

package ai.tuobot.sdk.network

import ai.tuobot.app.BuildConfig
import ai.tuobot.sdk.log.RLog
import android.content.Context
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.WebSocket
import okhttp3.WebSocketListener
import okio.ByteString
import java.io.File
import java.util.UUID
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicReference
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager


internal class WSConnection(val context: Context?, private val webSocketListenerToOut: WebSocketListener) {
    private val coroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
    private var WS_BASE_URL = BuildConfig.WS_BASE_URL
    companion object {
        private const val TAG = "WSConnection"
    }

    private val trustAllCertificates: TrustManager = object : X509TrustManager {
        override fun checkClientTrusted(chain: Array<out java.security.cert.X509Certificate>?, authType: String?) {
            // 不做任何操作，信任所有客户端证书
        }

        override fun checkServerTrusted(chain: Array<out java.security.cert.X509Certificate>?, authType: String?) {
            // 不做任何操作，信任所有服务器端证书
        }

        override fun getAcceptedIssuers(): Array<java.security.cert.X509Certificate> {
            return emptyArray() // 返回空数组表示信任所有颁发机构
        }
    }

    private val sslContext: SSLContext = SSLContext.getInstance("TLS").apply {
        init(null, arrayOf(trustAllCertificates), java.security.SecureRandom())
    }


    private val client = OkHttpClient.Builder()
        .pingInterval(30, TimeUnit.SECONDS) // 增加 ping 间隔时间
        .readTimeout(60, TimeUnit.SECONDS)  // 增加 read 超时时间
        .hostnameVerifier { _, _ -> true } // 忽略主机名验证
        .sslSocketFactory(sslContext.socketFactory, trustAllCertificates as X509TrustManager)
        .build()

    private val status = AtomicReference(Status.DISCONNECTED)

    private var webSocket: WebSocket? = null
    var lastSessionInfo: SessionInfo? = null


    fun connect(config: SessionConfig, baseUrl:String) {
        WS_BASE_URL = baseUrl
        webSocket?.cancel()
        status.set(Status.DISCONNECTED)
        val sessionId = UUID.randomUUID().toString()
        val sessionInfo = SessionInfo(sessionId, config).also {
            lastSessionInfo = it
        }

        webSocket = client.newWebSocket(buildRequest(sessionInfo), wsListener)

    }

    private fun buildRequest(info: SessionInfo): Request {
        val (sessionId, config) = info
        return Request.Builder()
            .url("$WS_BASE_URL/ws/${sessionId}?platform=web&" +
                    "use_search=${config.useSearch}" +
                    "&use_quivr=${config.useQuivr}" +
                    "&isJournalMode=${config.isJournalMode}" +
                    "&language=${config.language}" +
                    "&character_id=${config.characterId}" +
                    "&llm_model=${config.llmModel.raw}")
            .build()
    }

    private val wsListener = object : WebSocketListener() {
        override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
            RLog.d(TAG, "connection onClosed")
            webSocketListenerToOut.onClosed(webSocket,code,reason)

            if (webSocket != this@WSConnection.webSocket) return
            status.set(Status.DISCONNECTED)
        }

        override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
            RLog.e(TAG, "connection failed for: ${t.message}", t)
            webSocketListenerToOut.onFailure(webSocket,t,response)

            if (webSocket != this@WSConnection.webSocket) return
            status.set(Status.DISCONNECTED)

        }

        override fun onMessage(webSocket: WebSocket, text: String) {
            webSocketListenerToOut.onMessage(webSocket,text)
            RLog.d(TAG, "onMessage:${text}")

            if (webSocket != this@WSConnection.webSocket) return

        }

        override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
            RLog.d(TAG, "onMessage: bytes...")
            webSocketListenerToOut.onMessage(webSocket,bytes)

            if (webSocket != this@WSConnection.webSocket) return

        }

        override fun onOpen(webSocket: WebSocket, response: Response) {
            RLog.d(TAG, "connection onOpen")
            webSocketListenerToOut.onOpen(webSocket,response)

            if (webSocket != this@WSConnection.webSocket) return
            status.set(Status.CONNECTED)

        }
    }


    fun closeConn(){
        RLog.d(TAG, "closeConn()")
        if (null != webSocket){
            webSocket?.close(1000, "webSocket closeConn")
            webSocket = null
        }
    }

    enum class Status {
        DISCONNECTED,
        CONNECTING,
        CONNECTED,
    }

    fun sendMsg(newText: String?) {
        if (newText != null) {
            webSocket?.send(newText)
        }
    }

    fun sendMsg(byteS: ByteString) {
        if (byteS.size == 0)
            return
        webSocket?.send(byteS)
    }

    fun sendFile(file: File) {
        //audioTrackPlayer.clearAndStop()
        coroutineScope.launch {
            val fileBytes = readFileToByteArray(file)
            sendMsg(ByteString.of(*fileBytes))
        }

    }

    fun readFilePathToByteArray(filePath: String): ByteArray {
        val file = File(filePath)
        return file.readBytes()
    }

    fun readFileToByteArray(file: File): ByteArray {
        return file.readBytes()
    }

    fun getWebSocket(): WebSocket? {
        return webSocket
    }



}

private typealias SessionInfo = Pair<String, SessionConfig>
